#include "vd_net.h"

static int vd_net_udp_proc_thread_func(void *data)
{
    int recv_size = 0;
    vd_ssocket *vd_ssock = NULL;

    printk("vd_net_proc_thread_func started\n");

    vd_ssock = (vd_ssocket*) data;

    if (vd_ssock == NULL)
    {
        printk("vd_ssock NULL\n");
        goto out;
    }

    while (!kthread_should_stop()) {

        memset(vd_ssock->buff, 0, vd_ssock->buff_size);

        recv_size = vd_udp_recv(vd_ssock->sock, vd_ssock->buff, vd_ssock->buff_size);

        if (recv_size > 0)
        {
            vd_ssock->proc_func(vd_ssock->buff, recv_size);
        }
        else
        {
            schedule_timeout(HZ);
        }
    }

out:
    set_current_state(TASK_RUNNING);

    sock_release(vd_ssock->sock);
    vd_ssock->sock = NULL;

    printk("vd_net_udp_proc_thread_func ended\n");

    return 0;
}

int vd_start_server_udp_socket(vd_ssocket *vd_sock, unsigned int port)
{
    int err = 0;

    if (vd_sock == NULL)
    {
    err = -1;
    goto out;
    }

    /* create server socket */
    err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &(vd_sock->sock));
    if (err < 0)
    {
        printk(KERN_INFO ": Could not create a datagram socket, error = %d\n", -ENXIO);
        goto out;
    }

    memset(&vd_sock->addr, 0, sizeof(struct sockaddr));
    vd_sock->addr.sin_family = AF_INET;
    vd_sock->addr.sin_addr.s_addr = htonl(INADDR_ANY);
    vd_sock->addr.sin_port = htons(port);

    err = vd_sock->sock->ops->bind(vd_sock->sock, (struct sockaddr *)&vd_sock->addr, sizeof(struct sockaddr));
    if (err < 0)
    {
        printk(KERN_INFO ": Could not bind to socket, error = %d\n", -err);
        goto close_and_out;
    }
    else
    {
        printk(KERN_INFO ": listen port %d\n", port);
    }

    /* create proc thread */
    vd_sock->ts = kthread_run(vd_net_udp_proc_thread_func, vd_sock, "%s", "vd_net_udp_proc_thread_func");

    if (IS_ERR(vd_sock->ts))
    {
        printk(KERN_INFO ": unable to start kernel thread\n");
        kfree(vd_sock->ts);
        vd_sock->ts = NULL;

        goto close_and_out;
    }

    goto out;

close_and_out:
    sock_release(vd_sock->sock);
    vd_sock->sock = NULL;

out:
    return err;
}

int vd_close_server_udp_socket(vd_ssocket *vd_ssock)
{
    int err = 0;

    if (vd_ssock == NULL)
    {
        err = -1;
        return err;
    }

    kthread_stop(vd_ssock->ts);

    return err;
}

int vd_start_client_udp_socket(vd_csocket *vd_sock, char *ip, unsigned int port)
{
    int err = 0;

    if (vd_sock == NULL || ip == NULL)
    {
      err = -1;
      goto out;
    }

    /* create server socket */
    err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &(vd_sock->sock));
    if (err < 0)
    {
      printk(KERN_INFO ": Could not create a datagram socket, error = %d\n", -ENXIO);
      goto out;
    }

    memset(&vd_sock->addr, 0, sizeof(struct sockaddr));
    vd_sock->addr.sin_family = AF_INET;
    vd_sock->addr.sin_addr.s_addr = in_aton(ip);
    vd_sock->addr.sin_port = htons(port);

    err = vd_sock->sock->ops->connect(vd_sock->sock, (struct sockaddr *)&vd_sock->addr, sizeof(struct sockaddr), 0);
    if (err < 0)
    {
      printk(KERN_INFO ": Could not connect to socket, error = %d\n", -err);
      goto close_and_out;
    }

    goto out;

close_and_out:
    sock_release(vd_sock->sock);
    vd_sock->sock = NULL;

out:
    return err;
}

int vd_close_client_udp_socket(vd_csocket *sock)
{
  int err = 0;

  if (sock == NULL)
  {
    err = -1;
    goto out;
  }

  /* free allocated resources before exit */
  if (sock->sock != NULL)
  {
    sock_release(sock->sock);
    sock->sock = NULL;
  }

out:
  return err;
}


static int vd_net_tcp_proc_thread_func(void *data)
{
    int recv_size = 0;
    vd_ssocket *vd_ssock = NULL;

    printk("vd_net_proc_thread_func started\n");

    vd_ssock = (vd_ssocket*) data;

    if (vd_ssock == NULL)
    {
        printk("vd_ssock NULL\n");
        goto out;
    }

    while (!kthread_should_stop()) {

        memset(vd_ssock->buff, 0, vd_ssock->buff_size);

        recv_size = vd_tcp_recv(vd_ssock->sock, vd_ssock->buff, vd_ssock->buff_size);

        if (recv_size > 0)
        {
            vd_ssock->proc_func(vd_ssock->buff, recv_size);
        }
        else
        {
            schedule_timeout(HZ);
        }
    }

out:
    set_current_state(TASK_RUNNING);

    sock_release(vd_ssock->sock);
    vd_ssock->sock = NULL;

    printk("vd_net_udp_proc_thread_func ended\n");

    return 0;
}

int vd_start_server_tcp_socket(vd_ssocket *vd_sock, unsigned int port)
{
    int err = 0;
    int listennum = 2;

    if (vd_sock == NULL)
    {
        err = -1;
        goto out;
    }

    /* create server socket */
    err = sock_create(AF_INET, SOCK_STREAM, 0, &(vd_sock->sock));
    if (err < 0)
    {
        printk(KERN_INFO ": Could not create a datagram socket, error = %d\n", -ENXIO);
        goto out;
    }

    memset(&vd_sock->addr, 0, sizeof(struct sockaddr));
    vd_sock->addr.sin_family = AF_INET;
    vd_sock->addr.sin_addr.s_addr = htonl(INADDR_ANY);
    vd_sock->addr.sin_port = htons(port);

    err = vd_sock->sock->ops->bind(vd_sock->sock, (struct sockaddr *)&vd_sock->addr, sizeof(struct sockaddr));
    if (err < 0)
    {
        printk(KERN_INFO ": Could not bind to socket, error = %d\n", -err);
        goto close_and_out;
    }
    else
    {
        printk(KERN_INFO ": listen port %d\n", port);
    }

    err = vd_sock->sock->ops->listen(vd_sock->sock, listennum);

    if (err < 0)
    {
        printk(KERN_INFO ": Could not listen, error = %d\n", -err);
        goto close_and_out;
    }

    /* create proc thread */
    vd_sock->ts = kthread_run(vd_net_tcp_proc_thread_func, vd_sock, "%s", "vd_net_tcp_proc_thread_func");

    if (IS_ERR(vd_sock->ts))
    {
        printk(KERN_INFO ": unable to start kernel thread\n");
        kfree(vd_sock->ts);
        vd_sock->ts = NULL;

        goto close_and_out;
    }

    goto out;

close_and_out:
    sock_release(vd_sock->sock);
    vd_sock->sock = NULL;

out:
    return err;
}

int vd_close_server_tcp_socket(vd_ssocket *sock)
{
}

int vd_start_client_tcp_socket(vd_csocket *vd_sock, char *ip, unsigned int port)
{
}

int vd_close_client_tcp_socket(vd_csocket *sock)
{
}
